package fox.framework.rpc.endpoint.netty.server;

import fox.framework.rpc.common.StringUtils;
import fox.framework.rpc.common.WebUtils;
import fox.framework.rpc.exception.RpcDecodeWithRequestIdException;
import fox.framework.rpc.exception.RpcRecordException;
import fox.framework.rpc.message.MappedRpcHeader;
import fox.framework.rpc.message.RpcHeader;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.ByteProcessor;
import io.netty.util.ReferenceCountUtil;

import java.util.List;


/********************************************
 * protocol uri \n
 * headerKey:value;headerKey:value;\n
 * byte-body
 *********************************************/

/**
 * @author cuichao
 * @Description: FoxServerRequestDecoder
 * @Date: create in 2021/1/19 21:54
 */
public class FoxServerRequestDecoder extends ByteToMessageDecoder {

    private static final String PROTOCOL_NAME = "fox";
    private static final int PROTOCOL_MIN_LEN = 2;
    private static final String PROTOCOL_SPILT = "\n";
    private static final String PROTOCOL_ENTRY_SPILT = " ";
    private static final String HEADER_ENTRY_SPILT = ";";
    private static final String HEADER_KV_SPILT = ":";

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {

        MappedRpcHeader header = new MappedRpcHeader();
        /**
         * parse protocol
         */
        int protocol_len = in.forEachByte(ByteProcessor.FIND_LF);
        //not match
        if (protocol_len <= 0) {
            ReferenceCountUtil.retain(in);
            ctx.fireChannelRead(in);
            return;
        }
        byte[] protocolBuffer = new byte[protocol_len + 1];
        in.readBytes(protocolBuffer);
        String protocolBlock = new String(protocolBuffer, 0, protocolBuffer.length - 1);
        //以空格为间隔
        String[] protocols = protocolBlock.split(PROTOCOL_ENTRY_SPILT);
        if (protocols.length < PROTOCOL_MIN_LEN || !PROTOCOL_NAME.equals(protocols[0])) {
            ReferenceCountUtil.retain(in);
            in.resetReaderIndex();
            ctx.fireChannelRead(in);
            return;
        }
        String protocol = protocols[0];
        String path = protocols[1];
        /**
         * parse header
         */
        int header_len = in.forEachByte(protocol_len + 1, in.readableBytes(), ByteProcessor.FIND_LF) - protocol_len;
        if (header_len <= 0) {
            String msg = "the request-header is empty";
            throw new RpcRecordException(msg);
        }
        byte[] headerBuffer = new byte[header_len];
        in.readBytes(headerBuffer);
        String headerBlock = new String(headerBuffer, 0, headerBuffer.length - 1);
        String[] entries = headerBlock.split(HEADER_ENTRY_SPILT);
        for (String entry : entries) {
            String[] headerEntry = entry.split(HEADER_KV_SPILT);
            if (headerEntry.length == 2) {
                header.setHeader(headerEntry[0], WebUtils.urlDecode(headerEntry[1]));
            }
        }


        if (StringUtils.isEmpty(header.getHeader(RpcHeader.REQUEST_ID))) {
            String msg = "the request-id is empty";
            throw new RpcRecordException(msg);
        }

        /**
         * valid header must entry
         */
        if (StringUtils.isEmpty(header.getHeader(RpcHeader.CONTENT_LENGTH)) || StringUtils.isEmpty(header.getHeader(RpcHeader.CONTENT_TYPE))) {
            String msg = "the request header miss content-length or content-type";
            throw new RpcDecodeWithRequestIdException(protocol, header.getHeader(RpcHeader.REQUEST_ID), msg);
        }
        /**
         * read byte body
         */
        int contentLength;
        try {
            contentLength = Integer.valueOf(header.getHeader(RpcHeader.CONTENT_LENGTH)) > 0 ? Integer.valueOf(header.getHeader(RpcHeader.CONTENT_LENGTH)) : 0;
        } catch (NumberFormatException e) {
            String msg = "the content-length not be cast Integer";
            throw new RpcDecodeWithRequestIdException(protocol, header.getHeader(RpcHeader.REQUEST_ID), msg);
        }
        byte[] body = new byte[contentLength];

        if (contentLength > 0) {
            if (in.readableBytes() < contentLength) {
                ReferenceCountUtil.retain(in);
                in.resetReaderIndex();
                return;
            }
            in.readBytes(body);
        }
        /**
         * build request
         */

        RpcDecodeServerRequest request = new RpcDecodeServerRequest();
        request.setProtocol(protocol);
        request.setPath(path);
        request.setBodySource(body);

        for (String name : header.getHeaderNames()) {
            request.setHeader(name, header.getHeader(name));
        }


        out.add(request);

    }


}
